package serviceAdmin

import (
	"bytes"
	"context"
	"errors"
	"fmt"
	"mysqlAdmin/lib/log"
	"os"
	"os/exec"
	"path/filepath"

	"github.com/gogf/gf/v2/encoding/gyaml"
	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/os/gfile"
	"github.com/gogf/gf/v2/util/gconv"
	"gopkg.in/yaml.v3"
)

const (
	configFileName = "/opt/mysql/config.yaml"
)

type Configurations struct {
	Version  string    `yaml:"version"`
	Services []Service `yaml:"services"`
}
type Service struct {
	Port int    `yaml:"port"`
	Path string `yaml:"path"`
}

func (c *Configurations) Save() {
	serialContent, err := yaml.Marshal(&c)
	if err != nil {
		log.Errorf("配置文件对象序列化失败")
		return
	}

	file, err := gfile.OpenWithFlag(configFileName, 0665)
	if err != nil {
		log.Errorf("配置文件初始化失败")
		return
	}
	file.Write(serialContent)
	defer file.Close()

}

func ReadConfigurations() (*Configurations, error) {

	content := gfile.GetBytes(configFileName)
	if string(content) == "" {
		return NewConfigurations()
	}
	var config Configurations

	// log.Infof("配置文件内容:%s", string(content))

	// err := yaml.Unmarshal(content, config)
	// if err != nil {
	// 	log.Error("配置文件格式解析失败")
	// 	// return nil, errors.New("配置文件格式解析失败")
	// 	return NewConfigurations()
	// }

	err := gyaml.DecodeTo(content, &config)
	if err != nil {
		return nil, errors.New("配置文件格式解析失败")
	}

	return &config, nil
}

func NewConfigurations() (*Configurations, error) {
	file, err := gfile.Create(configFileName)
	if err != nil {
		log.Errorf("无法创建配置文件，请检查权限是否正确", err)
	}
	file.Close()

	config := Configurations{
		Version: "1.0",
	}
	config.Save()
	return &config, nil

}

func (c *Configurations) AddService(port int) (*Service, error) {
	service := Service{
		Port: port,
		Path: filepath.Join("/opt/mysql", gconv.String(port)),
	}

	c.Services = append(c.Services, service)
	c.Save()

	service.CreateDirectory()

	service.GenerateMyCNF()

	service.GenerateOSServiceConf()

	service.GenerateData()

	service.GenerateStartServiceScript()

	service.GenerateStopServiceScript()

	service.Chown()

	return &service, nil
}

func (s *Service) CreateDirectory() {

	// log
	logPath := filepath.Join(s.Path, "log")
	err := os.MkdirAll(logPath, 0775)
	if err != nil {
		log.Errorf("创建目录:%s失败,error:%v", logPath, err)
	}

	ownerCmd := exec.Command("chown", "-R", "mysql.mysql", logPath)
	if out, err := ownerCmd.Output(); err != nil {
		log.Errorf("创建目录权限修改失败,out:%v,err:%v ", out, err)
	}

	// sbin
	sbinPath := filepath.Join(s.Path, "sbin")
	err = os.MkdirAll(sbinPath, 0775)
	if err != nil {
		log.Errorf("创建目录:%s失败,error:%v", sbinPath, err)
	}

	ownerCmd = exec.Command("chown", "-R", "mysql.mysql", sbinPath)
	if out, err := ownerCmd.Output(); err != nil {
		log.Errorf("创建目录权限修改失败,out:%v,err:%v ", out, err)
	}

}

func (s *Service) Chown() {
	// 目录owner
	ownerCmd := exec.Command("chown", "-R", "mysql.mysql", s.Path)
	if out, err := ownerCmd.Output(); err != nil {
		log.Errorf("权限修改失败,out:%v,err:%v ", out, err)
	}
}

func (s *Service) GenerateMyCNF() {
	serviceConfigName := filepath.Join(s.Path, "my.cnf")

	mySQLHome := s.Path
	// mysqlDataHome := filepath.Join(s.Path, "data")
	mysqlPort := gconv.String(s.Port)

	template := `[client]
port    = {{.mysqlPort}}
socket = {{.mySQLHome}}/mysql.sock

[mysql]
prompt="\u@\h \R:\m:\s [\d]> "
no-auto-rehash

[mysqld]
user    = mysql
port    = {{.mysqlPort}}
admin_address = 127.0.0.1
basedir = /usr/local/mysql
datadir = {{.mySQLHome}}/data
socket    = {{.mySQLHome}}/mysql.sock
pid-file = {{.mySQLHome}}/mysqld.pid
character-set-server = utf8mb4
skip_name_resolve = 1

#这个就是用之前的身份认证插件
default-authentication-plugin = mysql_native_password

#保证日志的时间正确
log_timestamps = SYSTEM
#replicate-wild-ignore-table=mysql.%
replicate-wild-ignore-table=test.%
replicate-wild-ignore-table=information_schema.%
# Two-Master configure
#server-1
#auto-increment-offset = 1
#auto-increment-increment = 2
#server-2
#auto-increment-offset = 2
#auto-increment-increment = 2
# semi sync replication settings #
#plugin_dir = /usr/local/mysql/lib/mysql/plugin
#plugin_load = "validate_password.so;rpl_semi_sync_master=semisync_master.so;rpl_semi_sync_slave=semisync_slave.so"
plugin_dir =/usr/local/mysql/lib/plugin #官方版本的路径
plugin_load = "rpl_semi_sync_master=semisync_master.so;rpl_semi_sync_slave=semisync_slave.so" #官方版本的路径
slave_parallel_workers = 4
slave_parallel_type = LOGICAL_CLOCK
open_files_limit    = 65535
back_log = 1024
max_connections = 1024
max_connect_errors = 1000000
table_open_cache = 1024
table_definition_cache = 1024
table_open_cache_instances = 64
thread_stack = 512K
external-locking = FALSE
max_allowed_packet = 32M
sort_buffer_size = 4M
join_buffer_size = 4M
thread_cache_size = 1536
interactive_timeout = 600
wait_timeout = 600
tmp_table_size = 32M
max_heap_table_size = 32M
slow_query_log = 1
log_timestamps = SYSTEM
slow_query_log_file = {{.mySQLHome}}/log/slow.log
log-error = {{.mySQLHome}}/log/error.log
long_query_time = 0.1
log_queries_not_using_indexes =1
log_throttle_queries_not_using_indexes = 60
min_examined_row_limit = 100
log_slow_admin_statements = 1
log_slow_slave_statements = 1
server-id = {{.mysqlPort}}
log-bin = mysql-bin
sync_binlog = 1
binlog_cache_size = 4M
max_binlog_cache_size = 2G
max_binlog_size = 1G
binlog_expire_logs_seconds=2592000
#master_info_repository = TABLE
#relay_log_info_repository = TABLE
gtid_mode = on
enforce_gtid_consistency = 1
log_slave_updates
#slave-rows-search-algorithms = 'INDEX_SCAN,HASH_SCAN'
binlog_format = row
binlog_row_image=FULL
binlog_checksum = 1
relay_log_recovery = 1
relay-log-purge = 1
key_buffer_size = 32M
read_buffer_size = 8M
read_rnd_buffer_size = 4M
bulk_insert_buffer_size = 64M
myisam_sort_buffer_size = 128M
myisam_max_sort_file_size = 10G
myisam_repair_threads = 1
lock_wait_timeout = 3600
explicit_defaults_for_timestamp = 1
innodb_thread_concurrency = 0
innodb_sync_spin_loops = 100
innodb_spin_wait_delay = 30
#transaction_isolation = REPEATABLE-READ
transaction_isolation = READ-COMMITTED
#innodb_additional_mem_pool_size = 16M
innodb_buffer_pool_size = 2867M
innodb_buffer_pool_instances = 4
innodb_buffer_pool_load_at_startup = 1
innodb_buffer_pool_dump_at_shutdown = 1
innodb_data_file_path = ibdata1:1G:autoextend
innodb_flush_log_at_trx_commit = 1
innodb_log_buffer_size = 32M
innodb_log_file_size = 2G
innodb_log_files_in_group = 3
innodb_max_undo_log_size = 4G
# innodb_undo_directory = {{.mySQLHome}}/log/undolog
innodb_undo_tablespaces = 95

# 根据您的服务器IOPS能力适当调整
# 一般配普通SSD盘的话，可以调整到 10000 - 20000
# 配置高端PCIe SSD卡的话，则可以调整的更高，比如 50000 - 80000
innodb_io_capacity = 4000
innodb_io_capacity_max = 8000
innodb_flush_sync = 0
innodb_flush_neighbors = 0
innodb_write_io_threads = 8
innodb_read_io_threads = 8
innodb_purge_threads = 4
innodb_page_cleaners = 4
innodb_open_files = 65535
innodb_max_dirty_pages_pct = 50
innodb_flush_method = O_DIRECT
innodb_lru_scan_depth = 4000
innodb_checksum_algorithm = crc32
innodb_lock_wait_timeout = 10
innodb_rollback_on_timeout = 1
innodb_print_all_deadlocks = 1
innodb_file_per_table = 1
innodb_online_alter_log_max_size = 4G
innodb_stats_on_metadata = 0

# some var for MySQL 8
log_error_verbosity = 3
innodb_print_ddl_logs = 1
binlog_expire_logs_seconds = 2592000
#innodb_dedicated_server = 0
innodb_status_file = 1

# 注意: 开启 innodb_status_output & innodb_status_output_locks 后, 可能会导致log-error文件增长较快
innodb_status_output = 0
innodb_status_output_locks = 0
#performance_schema
performance_schema = 1
performance_schema_instrument = '%memory%=on'
performance_schema_instrument = '%lock%=on'
#innodb monitor
innodb_monitor_enable="module_innodb"
innodb_monitor_enable="module_server"
innodb_monitor_enable="module_dml"
innodb_monitor_enable="module_ddl"
innodb_monitor_enable="module_trx"
innodb_monitor_enable="module_os"
innodb_monitor_enable="module_purge"
innodb_monitor_enable="module_log"
innodb_monitor_enable="module_lock"
innodb_monitor_enable="module_buffer"
innodb_monitor_enable="module_index"
innodb_monitor_enable="module_ibuf_system"
innodb_monitor_enable="module_buffer_page"
innodb_monitor_enable="module_adaptive_hash"
#validate_password_policy=LOW

[mysqldump]
quick
max_allowed_packet = 32M

[mysqld_safe]
#malloc-lib=/usr/local/mysql/lib/jmalloc.so
nice=-19
open-files-limit=65535
`

	v := g.View()
	content, err := v.ParseContent(context.TODO(), template, g.Map{"mySQLHome": mySQLHome, "mysqlPort": mysqlPort})
	if err != nil {
		log.Errorf("配置文件渲染失败,error:%v", err)
	}
	gfile.PutContents(serviceConfigName, content)

}

func (s *Service) GenerateOSServiceConf() {
	serviceName := fmt.Sprintf("mysqld-%d.service", s.Port)
	serviceConfName := fmt.Sprintf("/usr/lib/systemd/system/%s", serviceName)
	mysqlDataHome := s.Path
	template := `[Unit]
Description=MySQL Server
Documentation=man:mysqld(8)
Documentation=http://dev.mysql.com/doc/refman/en/using-systemd.html
After=network.target
After=syslog.target
[Install]
WantedBy=multi-user.target
[Service]
User=mysql
Group=mysql
ExecStart=/usr/local/mysql/bin/mysqld_safe --defaults-file={{.mysqlDataHome}}/my.cnf
LimitNOFILE = 5000
#Restart=on-failure
#RestartPreventExitStatus=1
#PrivateTmp=false`
	v := g.View()
	content, err := v.ParseContent(context.TODO(), template, g.Map{"mysqlDataHome": mysqlDataHome})
	if err != nil {
		log.Errorf("服务配置文件渲染失败,error:%v", err)
	}
	gfile.PutContents(serviceConfName, content)

	reloadCmd := exec.Command("/usr/bin/systemctl", "daemon-reload")
	if out, err := reloadCmd.Output(); err != nil {
		log.Errorf("重新加载系统服务失败,out:%v,err:%v ", out, err)
	}

	enableServiceCmd := exec.Command("/usr/bin/systemctl", "enable", serviceName)
	if outEnable, err := enableServiceCmd.Output(); err != nil {
		log.Errorf("启用服务失败,out:%v,err:%v ", outEnable, err)
	}

}

func (s *Service) GenerateData() {
	// ${MYSQL_HOME}/bin/mysqld --defaults-file=${MYSQL_DATA_HOME}/my.cnf --initialize --user=mysql --basedir=${MYSQL_HOME} --datadir=${MYSQL_DATA_HOME}/data

	// data
	dataPath := filepath.Join(s.Path, "data")

	serviceConfigName := filepath.Join(s.Path, "my.cnf")
	mysqlDataHome := dataPath

	cmdString := []string{fmt.Sprintf("--defaults-file=%s", serviceConfigName), "--initialize", "--user=mysql", "--basedir=/usr/local/mysql", fmt.Sprintf("--datadir=%s", mysqlDataHome)}

	generateDataCmd := exec.Command("/usr/local/mysql/bin/mysqld", cmdString...)

	var outb, errb bytes.Buffer
	generateDataCmd.Stdout = &outb
	generateDataCmd.Stderr = &errb
	err := generateDataCmd.Run()
	if err != nil {
		log.Errorf("生成数据失败,err:%v ", err)
		fmt.Println("out:", outb.String(), "err:", errb.String())
	}

}

func (s *Service) GenerateStartServiceScript() {

	scriptFileName := filepath.Join(s.Path, "sbin", "start.sh")
	MYSQL_PORT := s.Port

	template := `export MYSQL_HOME=/usr/local/mysql
export MYSQL_PORT={{.MYSQL_PORT}}
export MYSQL_DATA_HOME=/opt/mysql/{{.MYSQL_PORT}}

${MYSQL_HOME}/bin/mysqld_safe --defaults-file=${MYSQL_DATA_HOME}/my.cnf &`
	v := g.View()
	content, err := v.ParseContent(context.TODO(), template, g.Map{"MYSQL_PORT": MYSQL_PORT})
	if err != nil {
		log.Errorf("启动脚本渲染失败,error:%v", err)
	}
	gfile.PutContents(scriptFileName, content)

	exeEnableCmd := exec.Command("/bin/chmod", "775", scriptFileName)
	if out, err := exeEnableCmd.Output(); err != nil {
		log.Errorf("启动脚本权限修改失败,out:%v,err:%v ", out, err)
	}

}
func (s *Service) GenerateStopServiceScript() {

	scriptFileName := filepath.Join(s.Path, "sbin", "stop.sh")
	MYSQL_PORT := s.Port

	template := `export MYSQL_HOME=/usr/local/mysql
export MYSQL_PORT={{.MYSQL_PORT}}
export MYSQL_DATA_HOME=/opt/mysql/{{.MYSQL_PORT}}

${MYSQL_HOME}/bin/mysqladmin -uroot -S ${MYSQL_DATA_HOME}/mysql.sock shutdown`
	v := g.View()
	content, err := v.ParseContent(context.TODO(), template, g.Map{"MYSQL_PORT": MYSQL_PORT})
	if err != nil {
		log.Errorf("启动脚本渲染失败,error:%v", err)
	}
	gfile.PutContents(scriptFileName, content)

	exeEnableCmd := exec.Command("/bin/chmod", "775", scriptFileName)
	if out, err := exeEnableCmd.Output(); err != nil {
		log.Errorf("启动脚本权限修改失败,out:%v,err:%v ", out, err)
	}

}
